package com.wyr.leetcode.step2.multiThread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 现有函数 printNumber 可以用一个整数参数调用，并输出该整数到控制台。
 * <p>
 * 例如，调用 printNumber(7) 将会输出 7 到控制台。
 * 给你类 ZeroEvenOdd 的一个实例，该类中有三个函数：zero、even 和 odd 。ZeroEvenOdd 的相同实例将会传递给三个不同线程：
 * <p>
 * 线程 A：调用 zero() ，只输出 0
 * 线程 B：调用 even() ，只输出偶数
 * 线程 C：调用 odd() ，只输出奇数
 * 修改给出的类，以输出序列 "010203040506..." ，其中序列的长度必须为 2n 。
 * <p>
 * 实现 ZeroEvenOdd 类：
 * <p>
 * ZeroEvenOdd(int n) 用数字 n 初始化对象，表示需要输出的数。
 * void zero(printNumber) 调用 printNumber 以输出一个 0 。
 * void even(printNumber) 调用printNumber 以输出偶数。
 * void odd(printNumber) 调用 printNumber 以输出奇数。
 * <p>
 * https://leetcode.cn/problems/print-zero-even-odd/description/
 */
public class ZeroEvenOdd1 {
    private volatile int count = 1;

    private ReentrantLock reentrantLock = new ReentrantLock();
    //是否已经打印 0
    private volatile boolean flag = false;

    Condition printZeroCondition = reentrantLock.newCondition();

    Condition evenOddCondition = reentrantLock.newCondition();


    private int n;

    public ZeroEvenOdd1(int n) {
        this.n = n;
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer2 printNumber) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            try {
                // 争抢锁
                reentrantLock.lock();
                // 抢到锁之后，发现flag为true，即已经打印了0，但是还没有打印奇数或偶数
                while (flag) {
                   // 等待在打印其他数字的条件上
                    evenOddCondition.await();
                }
                // 发现flag为false，即没有打印0，可以打印0
                printNumber.accept(0);
                flag = true;
                // 唤醒所有等待在打印0的条件上的线程
                printZeroCondition.signalAll();

            } finally {
                reentrantLock.unlock();
            }
        }
    }

    public void even(IntConsumer2 printNumber) throws InterruptedException {
        // 已经打印偶数的次数
        int printCount = 0;
        while (printCount < n/2) {
            try {
                reentrantLock.lock();
                // 抢到锁，但是flag为false，即没有打印0，等待在打印0的条件上
                while (!flag) {
                    printZeroCondition.await();
                }
                // 抢到锁，并且flag为true，并且当前数是偶数
                if (count % 2 == 0) {
                    //打印此偶数
                    printNumber.accept(count++);
                    printCount++;
                    //唤醒等待在打印其他数的条件上的线程
                    evenOddCondition.signalAll();
                    // 重置flag为false
                    flag = false;
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }


    public void odd(IntConsumer2 printNumber) throws InterruptedException {
        // 已经打印奇数的次数
        int printCount = 0;
        while (printCount < (n+1)/2) {
            try {
                reentrantLock.lock();
                // 抢到锁，但是flag为false，即没有打印0，等待在打印0的条件上
                while (!flag) {
                    printZeroCondition.await();
                }
                // 抢到锁，并且flag为true，并且当前数是奇数
                if (count % 2 != 0) {
                    //打印此奇数
                    printNumber.accept(count++);
                    printCount++;
                    //唤醒等待在打印其他数的条件上的线程
                    evenOddCondition.signalAll();
                    // 重置flag为false
                    flag = false;
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ZeroEvenOdd1 zeroEvenOdd = new ZeroEvenOdd1(3);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    zeroEvenOdd.zero(new IntConsumer2());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    zeroEvenOdd.even(new IntConsumer2());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    zeroEvenOdd.odd(new IntConsumer2());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t3").start();
    }
}

class IntConsumer1 {
    public void accept(int x) {
        System.out.println(x);
    }
}
